<?php
declare(strict_types=1);

namespace App\Controller;

use App\Game\Conf\GameProgress;
use App\Game\Conf\MainCmd;
use App\Game\Conf\SubCmd;
use App\Game\Core\Dispatch;
use App\Game\Core\Djs;
use App\Game\Core\Log;
use App\Game\Core\Packet;
use App\Game\Core\Robot;
use App\Game\Core\Room;
use App\Model\User;
use Hyperf\Contract\OnCloseInterface;
use Hyperf\Contract\OnMessageInterface;
use Hyperf\Contract\OnOpenInterface;
use Swoole\Http\Request;
use Swoole\Server;
use Swoole\Websocket\Frame;
use Swoole\WebSocket\Server as WebSocketServer;

class WebSocketController implements OnMessageInterface, OnOpenInterface, OnCloseInterface
{


    public function onMessage($server, Frame $frame): void
    {
//        $server->push($frame->fd, 'Recv: ' . $frame->data);
        //var_dump('消息链接：',$frame->fd);
        Log::show(" Message: client #{$frame->fd} push success Mete: \n{");
        $data = Packet::packDecode($frame->data);
//        if (isset($data['code']) && $data['code'] == 0 && isset($data['msg']) && $data['msg'] == 'OK') {
        Log::show('Recv <<<  cmd=' . $data['cmd'] . '  scmd=' . $data['scmd'] . '  len=' . $data['len'] . '  data=' . json_encode($data['data']));
        //转发请求，代理模式处理,websocket路由到相关逻辑
        $data['serv'] = $server;
        //用户登陆信息
        $game_conf = config('game');
        $user_info_key = sprintf($game_conf['user_info_key'], $frame->fd);
        $uinfo = redis()->get($user_info_key);
        if ($uinfo) {
            $data['userinfo'] = json_decode($uinfo, true);
        } else {
            $data['userinfo'] = array();
        }
        //var_dump('客户端：',$frame->fd);
        $obj = new Dispatch($data);
        $back = "<center><h1>404 Not Found</h1></center><hr><center>Swoole</center>\n";
        if (!empty($obj->getStrategy())) {
            $back = $obj->exec();
            if ($back) {
                $server->push($frame->fd, $back, WEBSOCKET_OPCODE_BINARY);

            }
        }
        //Log::show('Tcp Strategy <<<  data='.$back);
//        } else {
//            Log::show($data['msg']);
//        }
        Log::split('}');
    }


    /**
     * 登陆成功下发协议
     * @param $server
     * @param $fd
     */
    private function loginSuccess($server, $fd)
    {
        //原封不动发回去
        if ($server->getClientInfo($fd) !== false) {
            //查询用户是否在房间里面
            //$info = $this->getRoomData($account);
            $data = array('status' => 'login success', 'fd' => $fd);
            $data = Packet::packFormat('OK', 1, $data);
            $back = Packet::packEncode($data, MainCmd::CMD_SYS, SubCmd::LOGIN_SUCCESS_RESP);
            $server->push($fd, $back, WEBSOCKET_OPCODE_BINARY);
        }
    }


    public function onClose($server, int $fd, int $reactorId): void
    {
        $game_conf = config('game');
        $user_info_key = sprintf($game_conf['user_info_key'], $fd);
        $uinfo = redis()->get($user_info_key);
        if ($uinfo) {
            $uinfo = json_decode($uinfo, true);
        } else {
            $uinfo = array();
        }
        if (empty($uinfo)) {
            return;
        }
        $user_map_key = sprintf($game_conf['user_room'], $uinfo['account']);
        if (!redis()->exists($user_map_key)) {
            return;
        }
        $room_no = redis()->get($user_map_key);
        if ($room_no) {
            ddTitle('onclose user room_no:' . $room_no);
            $prepareArr = getGamePrepareALL($room_no);
            if (!empty($prepareArr)) {
                ddTitle('onclose set playerOffline:' . $uinfo['account']);
                //先设置玩家掉线状态
                playerOffline($room_no, $uinfo['account']);
            }
        }
        ddTitle('onclose user:' . (isset($uinfo['account']) ? $uinfo['account'] : 0));
        // 设置用户断开
        $this->setUserStatus($fd);
        // 断线踢出空房间
        $this->kickOutUser($server, $fd);
        //清除登陆信息变量
        $this->loginFail($fd, '3');
    }

    public function onOpen($server, Request $request): void
    {
        $fd = $request->fd;
        $game_conf = config('game');
        $query = $request->get;
        $redis = redis();
        $tokenKey = 'token:' . $query['token'];
        $token = redis()->get($tokenKey);
        if ($token) {
            $uinfo = json_decode($token, true);
            //允许连接， 并记录用户信息
            $uinfo['fd'] = $fd;
            $user_bind_key = sprintf($game_conf['user_bind_key'], $uinfo['account']);
            $last_fd = (int)$redis->get($user_bind_key);
            //之前信息存在，清除之前的连接
            if ($last_fd) {
                if ($server->exist($last_fd) && $server->isEstablished($last_fd)) {
                    //处理双开的情况
                    $this->loginFail($last_fd, '1');
                    $server->disconnect($last_fd);
                }
                // 断线踢出空房间
                //$this->kickOutUser($server,$uinfo['account']);
                //清理redis
                $redis->del($user_bind_key); //清除上一个绑定关系
                $redis->del(sprintf($game_conf['user_info_key'], $last_fd)); //清除上一个用户信息
            }
            // 设置连接成功状态
            $redis->hSet($game_conf['user_room_status'], (string)$uinfo['account'], 1);
            //保存登陆信息
            $redis->set($user_bind_key, $fd, $game_conf['expire']);
            //设置绑定关系
            $redis->set(sprintf($game_conf['user_info_key'], $fd), json_encode($uinfo), $game_conf['expire']);
            $this->loginSuccess($server, $fd);  //登陆成功
        } else {
            if ($server->exist($fd) && $server->isEstablished($fd)) {
                $this->loginFail($fd, '2');
                $server->disconnect($fd);
            }
        }
    }

    /**
     * 发送登陆失败请求到客户端
     * @param $fd
     * @param string $msg
     */
    private function loginFail($fd, $msg = '')
    {
        //原封不动发回去
        $server = server();
        if ($server->getClientInfo($fd) !== false) {
            $data = Packet::packFormat('OK', 0, array('data' => 'login fail' . $msg));
            $back = Packet::packEncode($data, MainCmd::CMD_SYS, SubCmd::LOGIN_FAIL_RESP);
            if ($server->exist($fd) && $server->isEstablished($fd)) {
                $server->push($fd, $back, WEBSOCKET_OPCODE_BINARY);
            }
        }
    }

    /**
     * 检查用户是否在游戏并设置托管状态0
     * @param $account
     */
    protected function checkSetTokenOver($account)
    {
        $game_conf = config('game');
        $redis = redis();
        $room_no_key = sprintf($game_conf['user_room'], $account);
        if ($redis->exists($room_no_key)) {
            $room_no = $redis->get($room_no_key);// 查询用户所在房间号
            $user_room_data_key = sprintf($game_conf['user_room_data'], $room_no);
            if ($redis->exists($user_room_data_key)) {
                $room_data_hash = redis()->hGetAll($user_room_data_key);//取出房间数据
                $user_room_data = json_decode($room_data_hash[$account], true);
                if ($user_room_data['take_over'] == 1) {
                    // 取消托管
                    $user_room_data['take_over'] = 0;
                    redis()->hSet($user_room_data_key, (string)$account, json_encode($user_room_data));
                }
            }
        }
    }

    /**
     * 断线状态设置
     * @param $fd
     */
    protected function setUserStatus($fd)
    {
        $game_conf = config('game');
        $redis = redis();
        $info_key = sprintf($game_conf['user_info_key'], $fd);
        if ($redis->exists($info_key)) {
            if ($redis->get($info_key)) {
                $user_info = json_decode($redis->get($info_key), true);
                $redis->hSet($game_conf['user_room_status'], (string)$user_info['account'], 0);
            }
        }
    }

    /**
     * 断线，踢出房间
     * @param $server
     * @param $fd
     */
    protected function kickOutUser($server, $fd)
    {
        $game_conf = config('game');
        $robot_conf = config('robot');
        $redis = redis();
        $user_uinfo_key = sprintf($game_conf['user_info_key'], $fd);
        if (!$redis->exists($user_uinfo_key)) {
            return;
        }
        $uinfo = json_decode($redis->get($user_uinfo_key), true);
        $account = $uinfo['account'];
        $user_map_key = sprintf($game_conf['user_room'], $uinfo['account']);
        if (!$redis->exists($user_map_key)) {
            return;
        }
        $room_no = $redis->get($user_map_key);

        // 所有座位号，等待房间，房间数据
        $room_wait_key = sprintf($game_conf['user_room_wait'], $room_no);
        $room_data_key = sprintf($game_conf['user_room_data'], $room_no);
        $progress = getGameProgress($room_no);
        // 如果已经开始游戏，则进入托管
        if ($redis->exists($room_data_key)) {
            ddTitle('user : ' . $account . ' on colose ,set take_over = 1');
            $user_data = redis()->hGet($room_data_key, (string)$account);
            if ($user_data) {
                $user_data = json_decode(redis()->hGet($room_data_key, (string)$account), true);
                $user_data['take_over'] = 1;
                redis()->hSet($room_data_key, (string)$account, json_encode($user_data));
            }
            $flag = true;
            $game_type = intval(redis()->hGet($room_data_key, 'game_type'));
            dd('game_type', $game_type);
            if ($game_type) {
                $flag = false;
            }
            dd('flag', $flag ? 1 : 0);
            //是否是在等待阶段或者准备阶段或者结算阶段，如果是的，则直接踢出该玩家
            if (in_array($progress, [GameProgress::GAME_JOIN, GameProgress::GAME_PREPARE,
                    GameProgress::GAME_CACULAR]) && $flag) {
                ddTitle('close on remove user');
                $chair_id = getChairIdbyAccount($room_no, $account);
                //从房间绑定集合中剔除该玩家
                removeRoomMapByAccount($account);
                removeRoomWaitByAccount($room_no, $account);
                removeRoomChairByAccount($room_no, $account);
                removeGamePrepareByAccount($room_no, $account);

                $robots = Robot::getRoomRobot($room_no);
                $robotNum = count($robots);
                $waiters = getRoomWait($room_no);
                $waitNum = count($waiters);
                if ($waitNum == 0 || $robotNum == $waitNum) {
                    clearRoomDataAll($room_no);
                    return;
                }
                //广播玩家退出消息
                $fds = getOtherFdsByRoomNo($room_no);
                $this->roomBroadcast($server, $chair_id, $fds);
                return;
            }
            return;
        }

        // 如果未开始游戏
        // 踢出wait
        if (!$redis->exists($room_wait_key)) {
            return;
        }

        //是否是在等待阶段或者准备阶段或者结算阶段，如果是的，则直接踢出该玩家
        if (in_array($progress, [GameProgress::GAME_JOIN, GameProgress::GAME_PREPARE,
            GameProgress::GAME_CACULAR]) ) {
            ddTitle('close on remove user 2');
            $chair_id = getChairIdbyAccount($room_no, $account);
            //从房间绑定集合中剔除该玩家
            removeRoomMapByAccount($account);
            removeRoomWaitByAccount($room_no, $account);
            removeRoomChairByAccount($room_no, $account);
            playerQuit($room_no, $account);
            removeGamePrepareByAccount($room_no, $account);

            $robots = Robot::getRoomRobot($room_no);
            $robotNum = count($robots);
            $waiters = getRoomWait($room_no);
            $waitNum = count($waiters);
            if ($waitNum == 0 || $robotNum == $waitNum) {
                clearRoomDataAll($room_no);
                return;
            }

            //广播玩家退出消息
            $fds = getOtherFdsByRoomNo($room_no);
            $this->roomBroadcast($server, $chair_id, $fds);
            return;
        }


        $waits = Room::getRoomWait($room_no);
        array_splice($waits, array_search($account, $waits), 1); //踢出当前掉线玩家
        // 如果剩下都是机器人
        $robots = Robot::getRoomRobot($room_no);
        $robotNum = count($robots);
        $waitNum = count($waits);
        if ($waitNum == 0 || $robotNum == $waitNum) {
            clearRoomDataAll($room_no);
            return;
        }

        $prepareArr = getGamePrepareALL($room_no);
        $flag = false;
        if (!empty($prepareArr)) {
            foreach ($prepareArr as $v) {
                if ($v == GameProgress::USER_OFFLINE) {
                    $flag = true;
                } else {
                    $flag = false;
                }
            }
        }
        if ($flag) { //全部不在线，直接清空房间
            clearRoomDataAll($room_no);
            return;
        }

        // 如果剩下的有真人和机器人
        $real_players = [];
        $robots = [];
        foreach ($waits as $item) {
            if (Robot::isRobot($room_no, $item)) {
                $robots[] = $item;
            } else {
                $real_players[] = $item;
            }
        }
        $reals = $this->getOnOffPlayers($real_players);
        if (empty($robots)) {
            if (empty($reals['on'])) {
//                $this->cleanRoom($reals['off'], $room_no);
                clearRoomDataAll($room_no);
                return;
            }
            if (!empty($reals['off'])) {
                // 不在线踢出
                foreach ($reals['off'] as $vvs) {
                    array_splice($real_players, array_search($vvs, $real_players), 1);
                    $self_chair = $this->cleanOne($vvs, $room_no, $real_players);
                    $room_fds = [];
                    foreach ($real_players as $k => $v) {
                        $bind_key = sprintf($game_conf['user_bind_key'], $v);
                        $room_fds[] = (int)$redis->get($bind_key);
                    }
                    // 房间广播
                    $this->roomBroadcast($server, $self_chair, $room_fds);
                }
            }
            return;
        }
        if (empty($reals['on'])) {
//            $this->cleanRoom($reals['off'], $room_no);
            clearRoomDataAll($room_no);
        }
    }

    // 清空房间
    protected function cleanRoom($users, $room_no)
    {
        $game_conf = config('game');
        $robot_conf = config('robot');
        $redis = redis();
        $room_chair_key = sprintf($game_conf['user_room_chair'], $room_no);
        $room_prepare_key = sprintf($game_conf['user_room_prepare'], $room_no);
        $room_wait_key = sprintf($game_conf['user_room_wait'], $room_no);
        $room_data_key = sprintf($game_conf['user_room_data'], $room_no);
        $room_robotLoop_key = sprintf($robot_conf['user_room_robot'], $room_no);
        $room_outRobot_key = sprintf($robot_conf['user_room_outRobot'], $room_no);
        $room_total_key = sprintf($game_conf['user_room_total'], $room_no);
        $user_room_color_key = sprintf($game_conf['user_room_color'], $room_no);  // 所有房间玩家定缺信息
        $user_room_change_key = sprintf($game_conf['user_room_change'], $room_no);  // 所有房间玩家换牌信息
        $room_progress_key = sprintf($game_conf['user_room_progress'], $room_no);
        foreach ($users as $v) {
            $map_key = sprintf($game_conf['user_room'], $v);
            $redis->del($map_key);
        }
        $redis->del($room_chair_key);
        $redis->del($room_prepare_key);
        $redis->del($room_wait_key);
        $redis->del($user_room_color_key);
        $redis->del($user_room_change_key);
        $redis->del($room_progress_key);
        $redis->del($room_data_key);
        $redis->del($room_robotLoop_key);
        $redis->del($room_outRobot_key);
        $redis->sRem($room_total_key, (string)$room_no);
    }

    protected function cleanOne($account, $room_no, $waits)
    {
        $game_conf = config('game');
        $redis = redis();
        $room_chair_key = sprintf($game_conf['user_room_chair'], $room_no);
        $room_prepare_key = sprintf($game_conf['user_room_prepare'], $room_no);
        $user_map_key = sprintf($game_conf['user_room'], $account);
        $room_wait_key = sprintf($game_conf['user_room_wait'], $room_no);
        $self_chair = null;
        // 踢出map
        if ($redis->exists($user_map_key)) {
            $redis->del($user_map_key);
        }
        // 踢出chair
        if ($redis->exists($room_chair_key)) {
            if ($redis->get($room_chair_key)) {
                $chairs = json_decode($redis->get($room_chair_key), true);
                $self_chair = $chairs[$account];
                unset($chairs[$account]);
                if (empty($chairs)) {
                    $redis->del($room_chair_key);
                } else {
                    $redis->set($room_chair_key, json_encode($chairs));
                }
            }
        }
        // 踢出prepare
        if ($redis->exists($room_prepare_key)) {
            $prepare = json_decode($redis->get($room_prepare_key), true);
            array_splice($prepare, array_search($account, $prepare), 1);
            if (empty($prepare)) {
                $redis->del($room_prepare_key);
            } else {
                $redis->set($room_prepare_key, json_encode($prepare));
            }
        }
        $redis->set($room_wait_key, json_encode($waits));
        return $self_chair;
    }

    /**
     * 游戏大厅房间数据
     * @return array
     */
    protected function hallData()
    {
        $game_conf = config('game');
        $room_total_key = $game_conf['user_room_total'];
        $fake_room_data = $this->fakeHallData();
        if (redis()->exists($room_total_key)) {
            $rooms = redis()->sMembers($room_total_key);
            $hall_rooms = array();
            $hall_datas = array();
            $wait_room_conf = $game_conf['user_room_wait'];
            $room_data_conf = $game_conf['user_room_data'];
            foreach ($rooms as $val) {
                $wait_room_key = sprintf($wait_room_conf, $val);
                $room_data_key = sprintf($room_data_conf, $val);
                // 正在等待的房间
                if (redis()->exists($wait_room_key)) {
                    $hall_rooms[$val] = json_decode(redis()->get($wait_room_key), true);
                }
                // 正在游戏的房间
                if (redis()->exists($room_data_key)) {
                    $room_data = redis()->hGetAll($room_data_key);
                    if (!isset($room_data['is_game_over']) || ($room_data['is_game_over'] == 0)) {
                        if (!empty($room_data['uinfo'])) {
                            $wait_users = [];   // 正在等待房间里的用户account
                            $room_data_uinfo = json_decode($room_data['uinfo'], true);
                            foreach ($room_data_uinfo as $ks => $vs) {
                                $wait_users[] = $ks;
                            }
                            $hall_rooms[$val] = $wait_users;
                        }
                    }
                }
            }
            if (!empty($hall_rooms)) {
                // 查询个房号用户信息
                $i = 0;
                foreach ($hall_rooms as $k => $v) {
                    if (!empty($v)) {
                        // 房间用户存在，查询用户信息
                        $curr_room_users = User::getUsers($v);
                        if ($curr_room_users['code'] == 0) {
                            $curr_room_data = $curr_room_users['data'];
                            foreach ($curr_room_data as $ks => $vs) {
                                $curr_room_data[$ks]['chair_id'] = $this->findChairId($k, $vs['account']);
                            }
                            $hall_datas[$i]['room_no'] = $k;
                            $hall_datas[$i]['room_data'] = $curr_room_data;
                        }
                        $i++;
                    }
                }
                $total_room_data = array_merge($fake_room_data, $hall_datas);
                return $total_room_data;
            } else {
                return $fake_room_data;
            }
        } else {
            return $fake_room_data;
        }
    }

    /**
     * 返回玩家房间桌位号
     * @param $room_no
     * @param $account
     * @return mixed
     */
    protected function findChairId($room_no, $account)
    {
        $game_conf = config('game');
        $room_chair_key = sprintf($game_conf['user_room_chair'], $room_no);
        $chair_data = json_decode(redis()->get($room_chair_key), true);
        return $chair_data[$account];
    }

    /**
     * 房间广播
     * @param $server
     * @param $chair_id
     * @param $fds
     */
    protected function roomBroadcast($server, $chair_id, $fds)
    {
        $msg = ['status' => '退出成功', 'chair_id' => $chair_id];
        $data = Packet::packFormat('OK', 1, $msg);
        $room_data = Packet::packEncode($data, MainCmd::CMD_SYS, SubCmd::SUB_GAME_ROOM_QUIT_RESP);
        foreach ($fds as $fd) {
            //获取客户端信息
            $fid = (int)$fd;
            $client_info = $server->getClientInfo($fid);
            $client[$fid] = $client_info;
            if (isset($client_info['websocket_status']) && $client_info['websocket_status'] == 3) {
                $server->push($fid, $room_data, WEBSOCKET_OPCODE_BINARY);
            }
        }
    }

    /**
     * 模拟大厅数据
     */
    protected function fakeHallData()
    {
        $game_conf = config('game');
        $fake_room_key = $game_conf['fake_room_hallFake'];
        $fake_expire = $game_conf['fake_expire'];
        if (redis()->exists($fake_room_key)) {
            $fake_room_data = json_decode(redis()->get($fake_room_key), true);
            return $fake_room_data;
        } else {
            $num = rand(10000, 99999);
            $data = User::getUserNum($num);
            $fake_room_data = [];
            $team_arr = array_chunk($data, 3);
            $room_no = 9989;
            foreach ($team_arr as $k => $v) {
                $team_data = [];
                if (!empty($v)) {
                    foreach ($v as $kk => $vv) {
                        $team_data[] = [
                            'account' => $vv['account'],
                            'score' => $vv['score'],
                            'avatar' => $vv['avatar'],
                            'chair_id' => (int)($kk + 1),
                        ];
                    }
                }
                $fake_room_data[] = ['room_data' => $team_data, 'room_no' => (int)($room_no + $k)];
            }
            // 缓存模拟房间数据
            redis()->set($fake_room_key, json_encode($fake_room_data, JSON_UNESCAPED_SLASHES));
            redis()->expire($fake_room_key, $fake_expire);
            return $fake_room_data;
        }
    }

    // 找出在线和不在线玩家
    protected function getOnOffPlayers($waits)
    {
        $game_conf = config('game');
        $redis = redis();
        $on_players = [];
        $off_players = [];
        foreach ($waits as $vv) {
            $user_status = $redis->hGet($game_conf['user_room_status'], (string)$vv);
            if (!empty($user_status) && ($user_status == 1)) {
                $on_players[] = $vv;
            } else {
                $off_players[] = $vv;
            }
        }
        return ['on' => $on_players, 'off' => $off_players];
    }

}
